import json
import logging
from datetime import datetime
from django.contrib.auth.models import User
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.db import transaction
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.authentication import TokenAuthentication
from rest_framework.parsers import JSONParser
from rest_framework.views import APIView
from api_test.common.api_response import JsonResponse
from api_test.common.common import record_dynamic
from api_test.common.decorator import catch_exception
from api_test.common_exception.exceptions import ParamsTypeErrorException, ParamsMissedException, \
    ObjectNotFoundException, NoPerMissionException, ObjectIsDeletedException
from api_test.models import Project, TestPlan, TestPlanCase
from api_test.serializers import TestPlanCaseDeserializer, TestPlanCaseSerializer

logger = logging.getLogger(__name__)  # 这里使用 __name__ 动态搜索定义的 logger 配置，这里有一个层次关系的知识点。


# 校验项目是否存在/是否禁用/操作员是否有权限
@catch_exception
def check_object(data, request):
    project_id = data.get('projectId')
    project = Project.objects.filter(id=project_id)
    if not project:
        raise ObjectNotFoundException('项目' + str(project_id))
    project_obj = project.filter(status=True)
    if not project_obj:
        raise ObjectIsDeletedException('项目' + str(project_id))
    if not request.user.is_superuser:
        raise NoPerMissionException(request.user.username)


class TestPlanCaseList(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    @swagger_auto_schema(manual_parameters=[
        openapi.Parameter(name='planId', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='测试计划id'),
        openapi.Parameter(name='caseId', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='测试计划用例id'),
        openapi.Parameter(name='page', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER, description='page 页数'),
        openapi.Parameter(name='page_size', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER,
                          description='pagesize 一页显示个数')
    ])
    @catch_exception
    def get(self, request):
        """
        获取测试计划用例集列表
        """
        try:
            page_size = int(request.GET.get('page_size', 20))
            current_page = int(request.GET.get('page', 1))
        except (TypeError, ValueError):
            return JsonResponse(code='999985', msg='page and page_size must be integer！')
        plan_id = request.GET.get('planId')
        case_id = request.GET.get('caseId')
        if not plan_id:
            raise ParamsMissedException('plan_id')
        if isinstance(plan_id, int):
            raise ParamsTypeErrorException('plan_id')
        if isinstance(case_id, int):
            raise ParamsTypeErrorException('case_id')
        plan = TestPlan.objects.filter(id=plan_id)
        if not plan:
            raise ObjectNotFoundException(f'测试计划{plan_id}')
        if not plan.filter(status=True):
            raise ObjectIsDeletedException(f'测试计划{plan_id}')
        case = TestPlanCase.objects.filter(plan_id=plan_id)
        if not case:
            return JsonResponse(code='999999', msg='success', data={'caseList': []})
        if not case.filter(status=True):
            return JsonResponse(code='999999', msg='success', data={'caseList': []})
        if not case_id:
            obj = case.filter(status=True).order_by('index')
            paginator = Paginator(obj, page_size)  # paginator对象
            page_sizes = paginator.num_pages  # 总页数
            total = paginator.count  # 总数
            try:
                obm = paginator.page(current_page)
            except PageNotAnInteger:
                obm = paginator.page(1)
            except EmptyPage:
                obm = paginator.page(paginator.num_pages)
            serialize = TestPlanCaseSerializer(obm, many=True)
            return JsonResponse(data={'caseList': serialize.data,
                                      'current_page': current_page,
                                      'total': total,
                                      'page_sizes': page_sizes,
                                      }, code='999999', msg='成功！')
        obj = case.filter(id=case_id)
        if not obj:
            raise ObjectNotFoundException(f'测试计划{plan_id}下测试用例{case_id}')
        if not obj.filter(status=True):
            raise ObjectIsDeletedException(f'测试计划{plan_id}下测试用例{case_id}')
        obi = obj.filter(status=True).order_by('index')
        serialize = TestPlanCaseSerializer(obi, many=True)
        return JsonResponse(code='999999', msg='success', data={'caseList': serialize.data})


class AddTestPlanCase(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    @catch_exception
    def parameter_check(self, data):
        """
        校验参数
        """
        for param in ['planId', 'projectId']:
            if not data.get(param):
                raise ParamsMissedException(param)
            if not isinstance(data.get(param), int):
                raise ParamsTypeErrorException(param)
        if not data.get('caseList'):
            raise ParamsMissedException('caseList')
        if not isinstance(data.get('caseList'), list):
            raise ParamsTypeErrorException('caseList')
        for caseInfo in data.get('caseList'):
            for key in ['index', 'name', 'request']:
                if not caseInfo.get(key):
                    raise ParamsTypeErrorException('caseList.' + key)
            if isinstance(caseInfo.get('request'), dict):
                caseInfo['request'] = json.dumps(caseInfo['request'])
            for param in ['responseAssert', 'globalVariable']:
                if isinstance(caseInfo.get(param), list):
                    caseInfo[param] = str(caseInfo[param])

    @swagger_auto_schema(request_body=openapi.Schema(
        type=openapi.TYPE_OBJECT,
        required=['projectId', 'planId', 'caseList'],
        properties={
            "projectId": openapi.Parameter(name='projectId', in_=openapi.IN_QUERY, type=openapi.TYPE_INTEGER,
                                           description='项目id'),
            'planId': openapi.Schema(type=openapi.TYPE_INTEGER, description='测试计划id'),
            'caseList': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(
                type=openapi.TYPE_OBJECT,
                required=['index', 'name', 'data'],
                properties={
                    'index': openapi.Schema(type=openapi.TYPE_INTEGER, description='用例执行顺序index'),
                    'name': openapi.Schema(type=openapi.TYPE_STRING, description='用例名称'),
                    'request': openapi.Schema(type=openapi.TYPE_OBJECT,
                                              required=['index', 'name', 'request'],
                                              properties={'url': openapi.Schema(type=openapi.TYPE_STRING,
                                                                                description='测试地址'),
                                                          'body': openapi.Schema(type=openapi.TYPE_OBJECT,
                                                                                 description='接口请求参数'),
                                                          'header': openapi.Schema(type=openapi.TYPE_OBJECT,
                                                                                   description='接口请求头信息'),
                                                          'method': openapi.Schema(type=openapi.TYPE_STRING,
                                                                                   description='接口请求方法')
                                                          }, description='请求参数'),
                    'responseAssert': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(
                        type=openapi.TYPE_OBJECT,
                        required=['key', 'value', 'index', 'flag'],
                        properties={
                            'logic': openapi.Schema(type=openapi.TYPE_STRING, default='assert断言', description='描述'),
                            'key': openapi.Schema(type=openapi.TYPE_STRING, description='断言字段路径表达式'),
                            'index': openapi.Schema(type=openapi.TYPE_STRING, default=0, description='响应结果索引'),
                            'value': openapi.Schema(type=openapi.TYPE_STRING, description='断言值'),
                            'flag': openapi.Schema(type=openapi.TYPE_STRING, default=True,
                                                   description='是否使用,默认为True')
                        }
                    )),
                    'globalVariable': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(
                        type=openapi.TYPE_OBJECT,
                        required=['key', 'value', 'index', 'flag'],
                        properties={
                            'logic': openapi.Schema(type=openapi.TYPE_STRING, default='json提取', description='描述'),
                            'key': openapi.Schema(type=openapi.TYPE_STRING, description='提取字段命名变量名'),
                            'index': openapi.Schema(type=openapi.TYPE_STRING, default=0, description='响应结果索引'),
                            'value': openapi.Schema(type=openapi.TYPE_STRING, description='提取字段路径表达式'),
                            'flag': openapi.Schema(type=openapi.TYPE_STRING, default=True,
                                                   description='是否使用,默认为True')
                        }
                    ))

                }
            ), description='测试用例列表'),

        },
    ))
    @catch_exception
    def post(self, request):
        """
        测试计划添加用例
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        obj = check_object(data, request)
        if obj:
            return obj
        project_id = data.get('projectId')
        plan_id = data.get('planId')
        name_list = [case['name'] for case in data.get('caseList')]
        plan_case = TestPlanCase.objects.filter(plan_id=plan_id, name__in=name_list)
        index_list = [case['index'] for case in data.get('caseList')]
        if len(set(index_list)) < len(index_list):
            return JsonResponse(code='999995', msg='存在重复的index,请检查!')
        if len(set(name_list)) < len(name_list):
            return JsonResponse(code='999995', msg='存在重复的name,请检查!')
        if plan_case:
            if plan_case.filter(status=True):
                return JsonResponse(code='999997', msg='已存在相同的用例名称!')
            elif plan_case.filter(status=False):
                plan_case.delete()
        if TestPlanCase.objects.filter(plan_id=plan_id, index__in=index_list, status=True):
            return JsonResponse(code='999995', msg='caseList包含已存在的index,请检查!')
        with transaction.atomic():
            serialize = TestPlanCaseDeserializer(data=data.get('caseList'), many=True)
            if serialize.is_valid():
                serialize.save(plan=TestPlan.objects.get(id=plan_id),
                               createUser=User.objects.get(id=request.user.pk),
                               updateUser=User.objects.get(id=request.user.pk))
                record_dynamic(project=project_id, _type='新增', operationObject='测试计划添加用例',
                               user=request.user.pk, data="测试计划添加用例成功")
                return JsonResponse(data={'caseList': [case['id'] for case in serialize.data]},
                                    code='999999', msg='成功！')


class UpdateTestPlanCase(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    @catch_exception
    def parameter_check(self, data):
        """
        校验参数
        """
        for param in ['id', 'index']:
            if not data.get(param):
                raise ParamsMissedException(param)
            if not isinstance(data.get(param), int):
                raise ParamsTypeErrorException(param)
        if not data.get('name'):
            raise ParamsMissedException('name')
        if not data.get('request'):
            raise ParamsMissedException('request')
        if not isinstance(data.get('name'), str):
            raise ParamsTypeErrorException('name')
        if isinstance(data.get('request'), dict):
            data['request'] = json.dumps(data['request'])
        for key in ['responseAssert', 'variables', 'globalVariable']:
            try:
                result = data[key]
            except KeyError:
                data[key] = []
            if isinstance(data.get(key), list):
                data[key] = str(data[key])

    @swagger_auto_schema(request_body=openapi.Schema(
        type=openapi.TYPE_OBJECT,
        required=['caseId', 'projectId', 'caseInfo'],

        properties={
            'id': openapi.Schema(type=openapi.TYPE_INTEGER, description='用例id'),
            'index': openapi.Schema(type=openapi.TYPE_INTEGER, description='用例执行顺序id'),
            'name': openapi.Schema(type=openapi.TYPE_STRING, description='用例名称'),
            'request': openapi.Schema(type=openapi.TYPE_OBJECT, description='用例请求body'),
            'header': openapi.Schema(type=openapi.TYPE_OBJECT, description='用例请求header'),
            'method': openapi.Schema(type=openapi.TYPE_STRING, description='接口请求method'),
            'responseAssert': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(
                type=openapi.TYPE_OBJECT,
                required=['key', 'value', 'index', 'flag'],
                properties={
                    'logic': openapi.Schema(type=openapi.TYPE_STRING, default='assert断言', description='描述'),
                    'key': openapi.Schema(type=openapi.TYPE_STRING, description='断言字段路径表达式'),
                    'index': openapi.Schema(type=openapi.TYPE_STRING, default=0, description='响应结果索引'),
                    'value': openapi.Schema(type=openapi.TYPE_STRING, description='断言值'),
                    'flag': openapi.Schema(type=openapi.TYPE_STRING, default=True,
                                           description='是否使用,默认为True')
                }
            )),
            'globalVariable': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(
                type=openapi.TYPE_OBJECT,
                required=['key', 'value', 'index', 'flag'],
                properties={
                    'logic': openapi.Schema(type=openapi.TYPE_STRING, default='json提取', description='描述'),
                    'key': openapi.Schema(type=openapi.TYPE_STRING, description='提取字段命名变量名'),
                    'index': openapi.Schema(type=openapi.TYPE_STRING, default=0, description='响应结果索引'),
                    'value': openapi.Schema(type=openapi.TYPE_STRING, description='提取字段路径表达式'),
                    'flag': openapi.Schema(type=openapi.TYPE_STRING, default=True,
                                           description='是否使用,默认为True')
                }
            ))
        },
    ))
    @catch_exception
    def post(self, request):
        """
        修改测试计划用例
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        case_id = data.get('id')
        case = TestPlanCase.objects.filter(id=case_id)
        if not case:
            raise ObjectNotFoundException(f'测试计划{case.plan_id}下测试用例{case_id}')
        if case.filter(status=False):
            raise ObjectIsDeletedException(f'测试计划{case.plan_id}下测试用例{case_id}')
        # if TestPlanCase.objects.exclude(id=case_id).filter(name=data.get('name'), plan_id=case[0].plan_id):
        #     return JsonResponse(code='999997', msg='存在相同的name!')
        if TestPlanCase.objects.exclude(id=case_id).filter(index=data.get('index'), plan_id=case[0].plan_id):
            return JsonResponse(code='999997', msg='存在相同的index!')
        project_id = TestPlan.objects.get(id=case[0].plan_id).project_id
        serialize = TestPlanCaseDeserializer(data=data)
        if serialize.is_valid():
            serialize.update(instance=case[0], validated_data=data)
            record_dynamic(project=project_id,
                           _type='修改', operationObject='测试计划', user=request.user.pk,
                           data=f'修改测试用例{case[0].name}')
            return JsonResponse(code='999999', msg='success', data={'caseId': case_id})
        print(serialize.errors)
        return JsonResponse(code='999995', msg=serialize.errors)


class DelTestPlanCase(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    @catch_exception
    def parameter_check(self, data):
        """
        校验参数
        """
        for param in ['planId', 'idList']:
            if not data.get(param):
                raise ParamsMissedException(param)
        if not isinstance(data.get('planId'), int):
            raise ParamsTypeErrorException('planId')
        if not isinstance(data.get('idList'), list):
            raise ParamsTypeErrorException('idList')
        for cid in data.get('idList'):
            if not isinstance(cid, int):
                raise ParamsTypeErrorException('idList列表选项')

    @swagger_auto_schema(request_body=openapi.Schema(
        type=openapi.TYPE_OBJECT,
        required=['planId', 'idList'],
        properties={
            'planId': openapi.Schema(type=openapi.TYPE_INTEGER, description='测试计划id'),
            'idList': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_INTEGER),
                                     description='测试用例id')
        },
    ))
    def post(self, request):
        """
        删除用例
        """
        data = JSONParser().parse(request)
        result = self.parameter_check(data)
        if result:
            return result
        plan_id = data.get('planId')
        plan = TestPlan.objects.filter(id=plan_id)
        project_id = plan[0].project_id
        if not plan:
            raise ObjectNotFoundException(f'测试计划{plan_id}')
        if not plan.filter(status=True):
            raise ObjectIsDeletedException(f'测试计划{plan_id}')
        id_list = data.get('idList')
        if len(set(id_list)) < len(id_list):
            return JsonResponse(code='999995', msg='idList存在重复的id!')
        case_list = TestPlanCase.objects.filter(plan_id=plan_id, id__in=id_list)
        if case_list.count() < len(id_list):
            case_id_list = [case.id for case in case_list]
            ids = ','.join([str(i) for i in sorted(set(id_list).difference(set(id_list) & set(case_id_list)))])
            return JsonResponse(code='999995', msg=f'测试计划{plan_id}用例id为{ids}的用例不存在,请检查!')
        if case_list.filter(status=True).count() < len(id_list):
            return JsonResponse(code='999995', msg='idList中含有已删除的id,请检查!')
        case_list.update(status=False, updateTime=datetime.now(), updateUser=request.user.pk)
        record_dynamic(project=project_id,
                       _type='删除', operationObject='测试计划用例', user=request.user.pk,
                       data=f"删除测试计划用例{data.get('idList')}")
        return JsonResponse(code='999999', msg='success', data={'idList': data.get('idList')})
